package com.dyx.source.bio;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Thread pool bio server
 *
 * https://bbs.huaweicloud.com/blogs/266248
 *
 * 使用线程池处理请求:为了防止连接请求过多，导致服务器创建的线程数过多，造成过多线程上下文切换的开销。可以通过线程池来限制创建的线程数
 * <p>
 * 但是引入了线程池，那么一个线程要处理多个连接的业务，线程在处理某个连接的 read 操作时，如果遇到没有数据可读，就会发生阻塞，
 * 那么线程就没办法继续处理其他连接的业务。
 * <p>
 *
 * 虽然我们采用线程池的方式来处理读写服务。但是这么做依然有很明显的弊端：
 * <p>
 * 同步阻塞IO，读写阻塞，线程等待时间过长
 * <p>
 * 在制定线程策略的时候，只能根据CPU的数目来限定可用线程资源，不能根据连接并发数目来制定，也就是连接有限制。
 * 否则很难保证对客户端请求的高效和公平。
 * <p>
 * 多线程之间的上下文切换，造成线程使用效率并不高，并且不易扩展
 * <p>
 * 状态数据以及其他需要保持一致的数据，需要采用并发同步控制
 *
 *
 * 要解决这一个问题，最简单的方式就是将 socket 改成非阻塞，然后线程不断地轮询调用 read 操作来判断是否有数据，这种方式虽然该能够解决阻塞的问题，
 * 但是解决的方式比较粗暴，因为轮询是要消耗 CPU 的，而且随着一个 线程处理的连接越多，轮询的效率就会越低。
 *
 * 上面的问题在于，线程并不知道当前连接是否有数据可读，从而需要每次通过 read 去试探。
 *
 * 那有没有办法在只有当连接上有数据的时候，线程才去发起读请求呢？答案是有的，实现这一技术的就是 I/O 多路复用。
 *
 * I/O 多路复用技术会用一个系统调用函数来监听我们所有关心的连接，也就说可以在一个监控线程里面监控很多的连接。
 *
 * @author dengyouxu
 * @date 2021.06.19 00:12:31:941
 */
public class ThreadPoolBIOServer {

    private static final Logger LOGGER = LoggerFactory.getLogger(ThreadPoolBIOServer.class);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(2345));
        } catch (IOException ex) {
            LOGGER.error("Listen failed", ex);
            return;
        }
        try{
            while(true) {
                Socket socket = serverSocket.accept();
                executorService.submit(() -> {
                    try{
                        InputStream inputstream = socket.getInputStream();
                        LOGGER.info("Received message {}", StreamUtils.copyToString(inputstream, Charset.defaultCharset()));
                    } catch (IOException ex) {
                        LOGGER.error("Read message failed", ex);
                    }
                });
            }
        } catch(IOException ex) {
            LOGGER.error("Accept connection failed", ex);
        }
    }

}
